package weka.classifiers.trees.IIDT;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Enumeration;

import weka.classifiers.Classifier;
import weka.core.Attribute;
import weka.core.Instance;
import weka.core.Instances;

public abstract class IIDTTreeClass extends Classifier implements IIDTTree {
	
	private static final long serialVersionUID = 1466037208174190846L;

	Instances nodeData = null;

	/** Attribute used for splitting. */
	protected Attribute m_Attribute = null;

	/** The node's successors. */ 
	protected IIDTTreeClass[] m_Successors = null;
	
	protected Attribute m_ClassAttribute = null;							//Classifying attribute.
	
	protected double m_ClassValue;								//Value of the classifying attribute.

	private Attribute[] ancestors = null;

	private IIDTTreeClass parent = null;

	private int nLeaves = 0;

	private int nNodes = 0;

	private int height = 0;
	
	

	public IIDTTreeClass() {
		// TODO Auto-generated constructor stub
	}

	@Override
	public Instances getExamples() {
		return nodeData;
	}

	@Override
	public IIDTTreeClass[] getSuccessors() {
		return m_Successors;
	}

	@Override
	public Attribute[] getAllAttributes(){
		Enumeration allAttEnum = nodeData.enumerateAttributes();
		int nAllAtt = nodeData.numAttributes() - 1;
		Attribute[] allAtt = new Attribute[nAllAtt];
		int i=0;
		while (allAttEnum.hasMoreElements()) {
			allAtt[i] = (Attribute) allAttEnum.nextElement();
			i++;
		}
		return allAtt;
	}

	@Override
	public Attribute[] getAncestors() {
		return ancestors;
	}

	@Override
	public int getAncestorsNumber() {
		Attribute[] ancestors = getAncestors();
		if(ancestors != null){
			return ancestors.length;
		}
		return 0;
	}

	@Override
	public void setAncestors(Attribute[] ancestors) {
		this.ancestors = ancestors;
	}	

	@Override
	public IIDTTreeClass getParent() {
		return parent;
	}

	@Override
	public void setParent(IIDTTreeClass parent) {
		this.parent = parent;
	}

	@Override
	abstract public int getLastR();

	@Override
	abstract public void setR(int r);

	@Override
	abstract public int getNextR();

	@Override
	public int getNLeaves() {
		return nLeaves;
	}

	@Override
	public void setNLeaves(int n) {
		nLeaves = n;		
	}

	@Override
	public int getNNodes() {
		return nNodes;
	}

	@Override
	public void setNNodes(int n) {
		nNodes = n;		
	}

	@Override
	public int getHeight() {
		return height;
	}

	@Override
	public void setHeight(int h) {
		height = h;		
	}

	@Override
	public double getUnode() {
		Attribute minDomainAtt = findMinDomainAttribute(getUnusedAttributes());
		int minDomainSize = minDomainAtt.numValues();
		int lBound = minDomainSize*minDomainSize;
		int deltaQ = this.getNLeaves() - lBound;

		return deltaQ/getCostNode();
	}

	@Override
	public double getCostNode() {
		int nAncestors = getAncestorsNumber();
		int nAllAttributes = nodeData.numAttributes() - 1;
		int nNonAncestors = nAllAttributes - nAncestors;
		int nExamples = nodeData.numInstances();

		return getNextR() * nExamples * 
				nNonAncestors * nNonAncestors * nNonAncestors;
	}

	@Override
	public Boolean isLeaf() {
		return (m_Attribute == null);
	}

	@Override
	public Boolean isRoot() {
		if(this.parent != null){
			return false;
		}
		return true;
	}

	@Override
	abstract public void buildClassifier(Instances data) throws Exception;

	/**
	 * Prints the decision tree using the private toString method from below.
	 *
	 * @return a textual description of the classifier
	 */
	public String toString() {

//		if ((m_Distribution == null) && (m_Successors == null)) {
		if (nNodes == 0) {
			return "No model built yet.";
		}
//		return "LookAheadAuxTree\n\n" + toString(0);
		return "";
	}

	/**
	 * Outputs a tree at a certain level.
	 *
	 * @param level the level at which the tree is to be printed
	 * @return the tree as string at the given level
	 */
	private String toString(int level) {

		StringBuffer text = new StringBuffer();

		if (m_Attribute == null) {
			if (Instance.isMissingValue(m_ClassValue)) {
				text.append(": null");
			} else {
				text.append(": " + m_ClassAttribute.value((int) m_ClassValue));
			} 
		} else {
			for (int j = 0; j < m_Attribute.numValues(); j++) {
				text.append("\n");
				for (int i = 0; i < level; i++) {
					text.append("|  ");
				}
				text.append(m_Attribute.name() + " = " + m_Attribute.value(j));
				text.append(m_Successors[j].toString(level + 1));
			}
		}
		return text.toString();
	}

	private Attribute[] getUnusedAttributes(){
		Attribute[] allAtt = getAllAttributes();
		Attribute[] usedAtt = getAncestors();

		if(usedAtt == null){
			return allAtt;
		}
		
//		ArrayList allAttList = new ArrayList(Arrays.asList(allAtt));
		ArrayList<Attribute> usedAttList = new ArrayList<Attribute>(Arrays.asList(usedAtt));
		ArrayList<Attribute> unusedAttList = new ArrayList<Attribute>(Arrays.asList(allAtt));
		unusedAttList.removeAll(usedAttList);

		return (Attribute[])unusedAttList.toArray(new Attribute[0]);
	}

	private static Attribute findMinDomainAttribute(Attribute[] attributes){
		Attribute minDomainAttribute = null;
		int minDomainSize = 0;

		for(Attribute att:attributes){
			int currDomainSize = att.numValues();
			if(currDomainSize > minDomainSize){
				minDomainAttribute = att;
				minDomainSize = currDomainSize;
			}
		}		
		return minDomainAttribute;
	}	

	Attribute[] findChildrenAncestors() {
		Attribute[] myAncestors = getAncestors();
		Attribute[] childrenAncestors = null;
		if(myAncestors == null){ // I am the root
			childrenAncestors = new Attribute[1];
			childrenAncestors[0] = m_Attribute;
		}else{		
			childrenAncestors = Arrays.copyOf(myAncestors, myAncestors.length + 1);
			childrenAncestors[myAncestors.length] = m_Attribute;
		}

		return childrenAncestors;
	}

}
